/*
 * Copyright (C) 2010-2011 Graz University of Technology, Austria. All Rights reserved. 
 *
 * Contact: Simone Kopeinik <simone.kopeinik@tugraz.at>
 * 	   Graz University of Technology, Knowledge Management Institute, Brückenkopfgasse 1/6, 8020 Graz 	
 * 	   <http://www.kmi.tugraz.at/>
 * 
 * This software is part of the TARGET platform developed by the
 * TARGET EC-funded project www.reachyourtarget.org
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The GNU General Public License is available at <http://www.gnu.org/licenses/>.
 *
 */
package at.tugraz.kmi.css.analyzer.gapvis.koichi;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.JPanel;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.functors.MapTransformer;
import org.apache.commons.collections15.map.LazyMap;

import at.tugraz.kmi.css.analyzer.CompetenceAnalyzer;
import at.tugraz.kmi.css.analyzer.UserCompetence;
import at.tugraz.kmi.css.analyzer.gapvis.CompetenceDropTransferHandler;
import at.tugraz.kmi.css.analyzer.gapvis.CompetenceProficiencyEditorVis;
import at.tugraz.kmi.css.analyzer.gapvis.GraphEdge;
import at.tugraz.kmi.css.analyzer.model.CompetenceAnalyzerModel;

import com.google.inject.Inject;
import com.jgoodies.binding.beans.PropertyConnector;
import com.jgoodies.binding.value.ValueModel;

import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.EditingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.PluggableGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer;

public class KiCurrentCompetenceProfilePane extends JPanel implements
		CompetenceProficiencyEditorVis {

	private static final long								serialVersionUID	= 1L;
	protected Graph<UserCompetence, GraphEdge>				graph;
	protected transient Layout<UserCompetence, GraphEdge>	layout;
	private VisualizationViewer<UserCompetence, GraphEdge>	viewer;

	private UserCompetence									newCompetence		= null; // new
																						// competence
																						// that
																						// are
																						// dragged
																						// from
																						// the
																						// list
	private final ArrayList<UserCompetence>					displayedCompetences;		// vertices
																						// (competences)
																						// that
																						// are
																						// already
																						// on
	protected final CompetenceAnalyzerModel					model;

	@Inject
	KiCurrentCompetenceProfilePane(CompetenceAnalyzerModel presModel) {
		this(presModel, presModel.getBean().getCurrentCompetenceProfile());
	}

	KiCurrentCompetenceProfilePane(CompetenceAnalyzerModel presModel,
			Graph<UserCompetence, GraphEdge> graph) {
		super();
		this.graph = graph;
		displayedCompetences = new ArrayList<UserCompetence>();
		model = presModel;
		initializeComponents();
		initEvents();
	}

	void initEvents() {
		ValueModel m = model
				.getModel(CompetenceAnalyzer.PROPERTY_CURRENT_COMPETENCE_PROFILE);
		PropertyConnector.connectAndUpdate(m, layout, "graph");
	}

	// Create a graph of competences.
	private void initializeComponents() {
		setBackground(Color.WHITE);

		// Create an undirected sparse graph of competences.

		// Create an layout of the graph.

		layout = new StaticLayout<UserCompetence, GraphEdge>(graph,
				new Dimension(600, 600));

		// Create a visualization viewer of the graph with the specified layout.
		viewer = new VisualizationViewer<UserCompetence, GraphEdge>(layout);
		viewer.setPreferredSize(new Dimension(720, 720));
		viewer.setBackground(Color.WHITE);
		// Configure how to render the graph viewer.
		// * Configure where to render vertex labels.
		viewer.getRenderer().getVertexLabelRenderer()
				.setPosition(Renderer.VertexLabel.Position.NE);
		// * Configure how to render vertex labels.
		viewer.getRenderContext().setVertexLabelTransformer(
				MapTransformer.<UserCompetence, String> getInstance(LazyMap
						.<UserCompetence, String> decorate(
								new HashMap<UserCompetence, String>(),
								new ToStringLabeller<UserCompetence>())));
		// * Configure how to render vertex icons.
		viewer.getRenderContext().setVertexIconTransformer(
				new CompetenceVertexIconTransformer(viewer));

		PluggableGraphMouse mouse = new PluggableGraphMouse();
		mouse.add(new PickingGraphMousePlugin<UserCompetence, String>());

		PopupMenuMousePlugin<UserCompetence, GraphEdge> plugin = new PopupMenuMousePlugin<UserCompetence, GraphEdge>();
		plugin.setVertexPopup(new CompetenceVertexPopup(null, this));
		mouse.add(plugin);
		Factory<UserCompetence> f1 = new Factory<UserCompetence>() {

			@Override
			public UserCompetence create() {
				return UserCompetence.of("");
			}
		};
		Factory<GraphEdge> f2 = new Factory<GraphEdge>() {

			@Override
			public GraphEdge create() {
				return new GraphEdge();
			}
		};

		EditingGraphMousePlugin emg = new EditingGraphMousePlugin<UserCompetence, GraphEdge>(
				f1, f2) {
			/**
			 * If the mouse is pressed in an empty area, create a new vertex
			 * there. If the mouse is pressed on an existing vertex, prepare to
			 * create an edge from that vertex to another
			 */
			@Override
			@SuppressWarnings("unchecked")
			public void mousePressed(MouseEvent e) {
				if (checkModifiers(e)) {
					final VisualizationViewer<UserCompetence, GraphEdge> vv = (VisualizationViewer<UserCompetence, GraphEdge>) e
							.getSource();
					final Point2D p = e.getPoint();
					GraphElementAccessor<UserCompetence, GraphEdge> pickSupport = vv
							.getPickSupport();
					if (pickSupport != null) {
						Graph<UserCompetence, GraphEdge> graph = vv.getModel()
								.getGraphLayout().getGraph();
						// set default edge type
						if (graph instanceof DirectedGraph) {
							edgeIsDirected = EdgeType.DIRECTED;
						} else {
							edgeIsDirected = EdgeType.UNDIRECTED;
						}

						final UserCompetence vertex = pickSupport.getVertex(vv
								.getModel().getGraphLayout(), p.getX(), p
								.getY());
						if (vertex != null) { // get ready to make an edge
							startVertex = vertex;
							down = e.getPoint();
							transformEdgeShape(down, down);
							vv.addPostRenderPaintable(edgePaintable);
							if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0
									&& vv.getModel().getGraphLayout()
											.getGraph() instanceof UndirectedGraph == false) {
								edgeIsDirected = EdgeType.DIRECTED;
							}
							if (edgeIsDirected == EdgeType.DIRECTED) {
								transformArrowShape(down, e.getPoint());
								vv.addPostRenderPaintable(arrowPaintable);
							}
						}
					}
					vv.repaint();
				}
			}

			private void transformArrowShape(Point2D down, Point2D out) {
				float x1 = (float) down.getX();
				float y1 = (float) down.getY();
				float x2 = (float) out.getX();
				float y2 = (float) out.getY();

				AffineTransform xform = AffineTransform.getTranslateInstance(
						x2, y2);

				float dx = x2 - x1;
				float dy = y2 - y1;
				float thetaRadians = (float) Math.atan2(dy, dx);
				xform.rotate(thetaRadians);
				arrowShape = xform.createTransformedShape(rawArrowShape);
			}

			/**
			 * code lifted from PluggableRenderer to move an edge shape into an
			 * arbitrary position
			 */
			private void transformEdgeShape(Point2D down, Point2D out) {
				float x1 = (float) down.getX();
				float y1 = (float) down.getY();
				float x2 = (float) out.getX();
				float y2 = (float) out.getY();

				AffineTransform xform = AffineTransform.getTranslateInstance(
						x1, y1);

				float dx = x2 - x1;
				float dy = y2 - y1;
				float thetaRadians = (float) Math.atan2(dy, dx);
				xform.rotate(thetaRadians);
				float dist = (float) Math.sqrt(dx * dx + dy * dy);
				xform.scale(dist / rawEdge.getBounds().getWidth(), 1.0);
				edgeShape = xform.createTransformedShape(rawEdge);
			}

		};
		AnnotatingGraphMousePlugin<UserCompetence, GraphEdge> agm = new AnnotatingGraphMousePlugin<UserCompetence, GraphEdge>(
				viewer.getRenderContext(), InputEvent.BUTTON2_MASK,
				InputEvent.BUTTON2_MASK);
		mouse.add(emg);
		mouse.add(agm);

		// dropListener = new DropCompetenceListener(this);
		// new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, dropListener);

		viewer.setGraphMouse(mouse);
		// viewer.addKeyListener(mouse.getModeKeyListener());

		viewer.setTransferHandler(new CompetenceDropTransferHandler(viewer,
				this));

		// JComboBox comboBox = mouse.getModeComboBox();

		setLayout(new BorderLayout());
		this.add(viewer, BorderLayout.CENTER);
		// this.add(comboBox, BorderLayout.SOUTH);
		addComponentListener(new java.awt.event.ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				Dimension size = getSize();
				layout.setSize(size);
				viewer.setSize(size);
				viewer.revalidate();
				revalidate();
			}

			@Override
			public void componentShown(java.awt.event.ComponentEvent evt) {
				Dimension size = getSize();
				layout.setSize(size);
				viewer.setSize(size);
				viewer.revalidate();
				revalidate();

			}
		});
	}

	// Add a new competence vertex to the viewer;
	// Callback from drop event listener.
	@Override
	public void addCompetence(UserCompetence competence, Point point) {
		if (competence != null && point != null) {
			if (!displayedCompetences.contains(competence)) {
				newCompetence = competence;
				Layout<UserCompetence, GraphEdge> newLayout = viewer.getModel()
						.getGraphLayout();
				graph.addVertex(newCompetence);
				newLayout.setLocation(newCompetence, viewer.getRenderContext()
						.getMultiLayerTransformer().inverseTransform(point));

				displayedCompetences.add(competence);
				newCompetence = null;
			}
		}
	}

	// Remove a competence vertex from the viewer.
	// Call back from popup menu.
	public void removeCompetence(UserCompetence competence) {
		if (competence != null) {
			if (displayedCompetences.contains(competence)) {
				viewer.getPickedVertexState().pick(competence, false);
				viewer.getGraphLayout().getGraph().removeVertex(competence);

				competence.reset();
				displayedCompetences.remove(competence);
				viewer.repaint();
			}
		}
	}

	// Switch visibility of the comment on a competence vertex on the viewer.
	// Call back from popup menu.
	public void switchVisibleComment(UserCompetence competence) {
		if (competence != null) {
			boolean visible = competence.isCommentVisible();
			competence.setCommentVisible(!visible);
			viewer.repaint();
		}
	}

	public void refresh() {
		viewer.repaint();
	}

	static class VertexFactory implements Factory<UserCompetence> {
		@Override
		public UserCompetence create() {
			return UserCompetence.of("CompetenceAnalyzerApplet");
		}
	}

	static class EdgeFactory implements Factory<GraphEdge> {
		@Override
		public GraphEdge create() {
			return new GraphEdge();
		}
	}
}
